package com.demo.webapp.ui.menu.taglib;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.demo.webapp.ui.ComponentBase;
import com.demo.webapp.ui.displayer.Displayer;
import com.demo.webapp.ui.menu.MenuComponent;
import com.demo.webapp.ui.menu.MenuRepositoryResource;
import com.demo.webapp.ui.menu.taglib.MenuDisplayerTag;
import com.demo.webapp.ui.menu.taglib.MenuTag;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.List;


/**
 * <p>
 * 设置标签所需的HTML hyperlink 'href'属性.
 * 这些属性默认来源于menu-config.xml配置文件.
 * 优先顺序为: 'location', 'page', 'forward';('action'以删除).
 *
 * <p>
 * 在配置文件中设置的&lt;Item&gt;节点中,可以添加属性'location', 'page', 'forward'
 * 'location'属性是优先最高的,将根据PageContext设置Menu的URL地址
 * 'page'属性优先级其次,根据page内容,设置location后,计算Menu的URL地址
 * 'forward'属性的优先级最低,用法与'page'相同,作为备用字段,以后处理。
 * 
 * 以下是一个配置例子:
 * <pre>
 * &lt;Menu name="indexMenuMore" title="More Examples"&gt;
 *   &lt;Item name="actionExample"  title="Example - 'action' attribute"
 *         <B>location="/menu/bullet"</B>/&gt;
 *   &lt;Item name="pageExample" title="Example - 'page' attribute"
 *         <B>page="/bulletmenu.jsp"</B>/&gt;
 * &lt;/Menu&gt;
 * </pre>
 * @author ryuu.kk
 * @version 1.0 $Date: 2012/02/20 at 17:16:39$ 
 */
public class MenuTag extends TagSupport {
    /**
	 * 版本序列化ID
	 */
	private static final long serialVersionUID = 2591021208844687134L;
	
	//~ Instance fields ========================================================
    protected final static Log log = LogFactory.getLog(MenuTag.class);
    private String name;
    private String target;
    
    //~ Methods ================================================================
    public void setName(String name) {
        this.name = name;
    }

    public void setTarget(String target) {
        this.target = target;
    }

    @SuppressWarnings("unchecked")
	public int doStartTag() throws JspException {
    	
    	//取得显示对象
        Displayer<MenuComponent> displayer = (Displayer<MenuComponent>) pageContext.getAttribute(MenuDisplayerTag.DISPLAYER_KEY);

        if (displayer == null) {
            throw new JspException("Could not retrieve the menu displayer.");
        }

        //取得被MenuDisplayerTag设置的私有对象容器
        MenuRepositoryResource repository = (MenuRepositoryResource) pageContext.getAttribute(MenuDisplayerTag.PRIVATE_REPOSITORY);

        if (repository == null) {
            throw new JspException("Could not obtain the menu repository");
        }
        
        //取得Menu组件
        MenuComponent menu = repository.getComponent(this.name);

        if (menu != null) {
            try {
                // 重写目标地址
                if (target != null) {
                    displayer.setTarget(this.target);
                }

                // 设置location值(当没有指定该值时,进行计算) 
                try {
                    setPageLocation(menu);
                } catch (MalformedURLException m) {
                    log.error("Incorrect action or forward: " + m.getMessage());
                    log.warn("Menu '" + menu.getName() + "' location set to #");
                    menu.setLocation("#");
                } 
                displayer.display(menu);
                displayer.setTarget(null);
            } catch (Exception e) {
                e.printStackTrace();
                throw new JspException(e);
            }
        } else {
            String error = MenuDisplayerTag.messages.getString("menu.not.found") +
                    " " + this.name;
            log.warn(error);
            try {
                pageContext.getOut().write(error);
            } catch (IOException io) {
                throw new JspException(error);
            }
        }

        return SKIP_BODY;
    }
    public int doEndTag() throws JspException {

    	return EVAL_PAGE;
    }
    /**
     * 当location为空时，设置location地址
     * 如果page不为空,则使用page地址作为location地址
     * 如果page也为空,则使用forward地址作为location地址
     * FIXME
     * 根据通配符${}查找相应数据
     *
     * @param menu Menu组件对象
     */
    protected void setPageLocation(ComponentBase menu) throws MalformedURLException, JspException {
        HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
        setLocation(menu);
        String url = menu.getLocation();

        //检查是有存在EL通配符
        if ((url != null) && (url.indexOf("${") > -1)) {
            String queryString = null;

            if (url.indexOf("?") > -1) {
                queryString = url.substring(url.indexOf("?") + 1);
                url = url.substring(0, url.indexOf(queryString));
            }
            // variable is in the URL
            if (queryString != null) {
                menu.setUrl(url + parseString(queryString, request));
            } else {
                // parse the URL, rather than the queryString
                menu.setUrl(parseString(url, request).toString());
            }
        } else {
            menu.setUrl(url);
        }

        HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
        if (menu.getUrl() != null) {
            menu.setUrl(response.encodeURL(menu.getUrl()));
        }

        // do all contained menus
        List<ComponentBase> subMenus = menu.getSubComponents(); 

        if (subMenus != null && subMenus.size() > 0) {
            for (ComponentBase subMenu : subMenus) {
                this.setPageLocation(subMenu);
            }
        }
    }

    protected void setLocation(ComponentBase menu) throws MalformedURLException {
        // if the location attribute is null, then set it with a context relative page
        // attribute if it exists
        if (menu.getLocation() == null) {
            try {
                if (menu.getPage() != null) {
                    HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
                    menu.setLocation(request.getContextPath() + getPage(menu.getPage()));
                    HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
                    menu.setLocation(response.encodeURL(menu.getLocation()));
                } else if (menu.getForward() != null) {
                    HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
                    menu.setLocation(request.getContextPath() + getPage(menu.getForward()));
                    HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
                    menu.setLocation(response.encodeURL(menu.getLocation()));
                }
            } catch (NoClassDefFoundError e) {
                if (menu.getForward() != null) {
                    throw new MalformedURLException("forward '" + menu.getForward() + "' invalid - no struts.jar");
                } 
            }
        }
    }

    /**
     * 返回page属性
     * 如果page没有设置"/",将page增加一个 "/"
     *
     * @param page page属性
     */
    protected String getPage(String page) {
//        if (page.startsWith("/")) {
//            return page;
//        } else {
//            page = "/" + page;
//        }

        return page;
    }
    
    private StringBuffer parseString(String str, HttpServletRequest request) {
        StringBuffer sb = new StringBuffer();
        
        while (str.indexOf("${") >= 0) {
            sb.append(str.substring(0, str.indexOf("${")));

            String variable = str.substring(str.indexOf("${") + 2, str.indexOf("}"));
            //对page,request,session（如果有效的话）和application范围进行查找
            String value = String.valueOf(pageContext.findAttribute(variable)); 
            
            if (value == null) {
                //如果不存在,则查看请求参数
                value = request.getParameter(variable);
            }

            //value仍然为空,使用Struts2的值栈? no.
            if (value == null) {
                log.warn("Value for '" + variable +
                        "' not found in pageContext or as a request parameter");
            }

            sb.append(value);
            str = str.substring(str.indexOf("}") + 1, str.length());
        }

        return sb.append(str);
    }

    public void release() {
        this.name = null;
        this.target = null;
    }
}
